﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data;
using System.IO;
using ActivePalInterop.Data;
using ActivePalInterop.Events;
using System.ComponentModel;


namespace ActivePalInterop
{
    /// <summary>
    /// Reads data from a text file and writes it down to a database
    /// </summary>
    public class Uploader
    {
        #region Event ReadRowsPulse

        /// <summary>
        /// Occurs when reader processed a certain ammount of lines = PulseReadCount
        /// </summary>
        public event ReadRowsPulseEventHandler ReadRowsPulse;

        /// <summary>
        /// Invokes ReadRowsPulse event
        /// </summary>        
        public void InvokeReadRowsPulse(string majorText, string minorText)
        {
            if (ReadRowsPulse != null)
            {
                ReadRowsPulse(this, new ProgressBarLablesEventArgs(majorText, minorText));
            }
        }

        #endregion

        #region Event BulkInsertPulse

        /// <summary>
        /// Occurs when reader processed a certain ammount of lines = PulseReadCount
        /// </summary>
        public event ProgressControlEventHandler BulkInsertPulse;

        /// <summary>
        /// Invokes ReadRowsPulse event
        /// </summary>        
        public void InvokeBulkInsertPulse(int min, int max, int current, string major, string minor)
        {
            if (BulkInsertPulse != null)
            {
                BulkInsertPulse(this, new ProgressControlEventArgs(min, max, current, major, minor));
            }
        }

        #endregion

        #region Event DebugMessage

        /// <summary>
        /// Generates a debug message that can be added to a GUI output
        /// </summary>
        public event MessageEventHandler DebugMessage;

        /// <summary>
        /// Invokes DebugMessage event
        /// </summary>        
        public void InvokeDebugMessage(string message)
        {
            if (DebugMessage != null)
            {
                DebugMessage(this, new MessageEventArgs(message));
            }
        }

        #endregion

        #region Properties

        /// <summary>
        /// Indicates when a ReadRowsPulse even occurs 
        /// </summary>
        public int PulseReadCount { get; set; }

        /// <summary>
        /// Indicates the ammount of records added to InternalDataTable. It varies from 0 to CommitBatchSize.
        /// </summary>
        public int RecordCount { get; set; }

        /// <summary>
        /// Indicates the ammount of records to be commited with one query
        /// </summary>
        public int CommitBatchSize { get; set; }

        /// <summary>
        /// Db table name where we write data to
        /// </summary>
        public string TableName { get; set; }

        /// <summary>
        /// Local cache storage for temp data that is sent to DB. Contains from 0 to CommitBatchSize rows
        /// </summary>
        public DataTable InternalDataTable { get; set; }

        /// <summary>
        /// Stores all the values read from a file
        /// </summary>
        public List<ActivePalRecord> Records { get; set; }

        /// <summary>
        /// Indicates how many rows will be imported to a database
        /// </summary>
        public int RecordsCount { get { return Records.Count; } }

        /// <summary>
        /// Shows how many records has been already sent to database
        /// </summary>
        public int ImportedRecordsCount { get; set; }

        /// <summary>
        /// Gets or sets a current session object that is used for operations
        /// </summary>
        public Session CurrentSession { get; set; }

        /// <summary>
        /// Gets or sets a current person object that is used for operations
        /// </summary>
        public Person CurrentPerson { get; set; }

        #endregion

        #region Constructor


        /// <summary>
        /// Initialises a new object
        /// </summary>
        /// <param name="tableName">Database table name</param>
        /// <param name="commitBatchSize">How many records will be inserted in one query</param>
        /// <param name="columns">Columns of the DataTable in a Db</param>
        /// <param name="p">Person object</param>
        public Uploader(string tableName, int commitBatchSize, DataColumnCollection columns, Person p)
        {
            Records = new List<ActivePalRecord>();
            InternalDataTable = new DataTable(tableName);
            TableName = tableName;
            RecordCount = 0;
            ImportedRecordsCount = 0;
            CommitBatchSize = commitBatchSize;
            InvokeDebugMessage("CommitBatchSize: " + commitBatchSize);
            PulseReadCount = 10000;
            InvokeDebugMessage("PulseReadCount: " + PulseReadCount);
            CurrentSession = new Session(p.PersonId);
            CurrentPerson = p;

            // add columns to this data table
            foreach (DataColumn column in columns)
            {
                InternalDataTable.Columns.Add(column.ColumnName, column.DataType);
            }

        }

        #endregion



        /// <summary>
        /// Reads data from a .csv file and fills a local cache DataTable object
        /// </summary>
        /// <param name="reader">Reader to read from</param>
        /// <param name="details">Concrete details are expected here</param>        
        /// <param name="worker">Reference to the callee worker</param>
        /// <param name="e">param to indicate cancelation</param>
        /// <returns>rows count</returns>
        public int Load(StreamReader reader, IUploadableData details, BackgroundWorker worker, DoWorkEventArgs e)
        {
            InvokeDebugMessage("Start reading csv file.");

            //skip header            
            //int headerRows = 5;
            int errors = 0;
            
            string dataFile = "Data file: ";
            string monitorSerial = "Monitor serial number: ";

            for (int i = 0; i < details.SkippedHeaderCount; i++)
            {
                string header = reader.ReadLine();
                
                int h1 = header.IndexOf(dataFile);
                if (h1 > 0)
                {
                    header = header.Remove(0, dataFile.Length);
                    CurrentSession.DataFilePath = header.Substring(h1, header.Length - 1).Replace("\"",String.Empty);
                }

                int h2 = header.IndexOf(monitorSerial);
                if (h2 > 0)
                {
                    header = header.Remove(0, monitorSerial.Length);
                    CurrentSession.MonitorSerialNumber = header.Substring(h2, header.Length - 1).Replace("\"", String.Empty);
                }
            }

            InvokeDebugMessage("Skipped headerRows: " + details.SkippedHeaderCount);
            
            //populating inner datatable with values from a file
            InvokeReadRowsPulse("Reading data from file ", String.Empty);
            string line;

            DateTime start = DateTime.Now;
            Guid sessionId = CurrentSession.SessionId;
            
            while ((line = reader.ReadLine()) != null)
            {
                if ((worker.CancellationPending == true))
                {
                    e.Cancel = true;
                    break;
                }

                //a line from a file
                string[] raw;
                DateTime date;
                byte value;

                try
                {
                    raw = line.Split(new char[] { ',' });

                    //clean data
                    date = Convert.ToDateTime(raw[0].Trim(new char[] { '#' }));
                    value = Convert.ToByte(raw[1]);

                    //add to a internal datatable
                    ActivePalRecord r = new ActivePalRecord(sessionId, date, value);
                    Records.Add(r);
                }
                catch (Exception ex)
                {
                    // line can't be parsed
                    errors++;
                    InvokeDebugMessage("Error in line: " + line);
                }

                if (RecordsCount % PulseReadCount == 0)
                {
                    InvokeReadRowsPulse("Reading data from the file ", RecordsCount + " rows.");
                }
            }
            DateTime stop = DateTime.Now;
            TimeSpan duration = stop - start;

            InvokeDebugMessage("Error numbers: " + errors);
            InvokeDebugMessage("Finish uploading in: " + duration.Minutes + " mins " + duration.Seconds + " secs.");

            return RecordsCount;
        }

        /// <summary>
        /// Uploads data from local cached DataTable to a database
        /// </summary>
        /// <param name="worker">Reference to the callee worker</param>
        /// <param name="e">param to indicate cancelation</param>
        public void Flush(BackgroundWorker worker, DoWorkEventArgs e)
        {
            PrepareDatabaseForUpload();            
            string majorProgress = "Uploading data to the database.";
            string minorProgress = "{0} rows";

            InvokeDebugMessage("Start reading file into cache.");
            DateTime start = DateTime.Now;
            // transfer data to the datatable
            foreach (ActivePalRecord record in Records)
            {
                //add record to a cached inner datatable
                PopulateDataTable(record);

                //Commit if needed
                if (RecordCount >= this.CommitBatchSize)
                {
                    if ((worker.CancellationPending == true))
                    {
                        e.Cancel = true;
                        break;
                    }

                    //use bulk insert to copy a cached DataTable to a database
                    DbFacade.GetInstance().WriteToDatabase(this.TableName, InternalDataTable);

                    //increment imported rows count
                    ImportedRecordsCount += InternalDataTable.Rows.Count; ;

                    //pulse progress bar event event
                    InvokeBulkInsertPulse(0, RecordsCount, ImportedRecordsCount, 
                        majorProgress, String.Format(minorProgress, ImportedRecordsCount));


                    //clear cache
                    ClearInternalDataTable();
                }
            }
            // write remaining records to the DB
            if (RecordCount > 0)
            {
                //use bulk insert to copy a cached DataTable to a database
                DbFacade.GetInstance().WriteToDatabase(this.TableName, InternalDataTable);

                //increment imported rows count
                ImportedRecordsCount += InternalDataTable.Rows.Count; ;

                //pulse an event
                InvokeBulkInsertPulse(0, RecordsCount, ImportedRecordsCount,
                        majorProgress, String.Format(minorProgress, ImportedRecordsCount));

                //clear cache
                ClearInternalDataTable();
            }

            DateTime stop = DateTime.Now;
            TimeSpan duration = stop - start;            
            InvokeDebugMessage("Finish reading file: " + duration.Minutes + " mins " + duration.Seconds + " secs.");
            e.Result = ImportedRecordsCount + " rows imported.";
        }

        /// <summary>
        /// Adds a row to a local cached DataTable
        /// </summary>
        /// <param name="record"></param>
        private void PopulateDataTable(ActivePalRecord record)
        {
            DataRow row = InternalDataTable.NewRow();

            row[0] = record.SessionId;
            row[1] = record.Date;
            row[2] = record.Value;

            // add it to the base for final addition to the DB
            InternalDataTable.Rows.Add(row);
            RecordCount++;
        }

        /// <summary>
        /// Clears local cache and sets the RecordCount to 0
        /// </summary>
        private void ClearInternalDataTable()
        {
            // reset
            InternalDataTable.Clear();
            RecordCount = 0;
        }

        /// <summary>
        /// Update CurrentSession object with dates, create session in the db
        /// </summary>
        private void PrepareDatabaseForUpload()
        {
            CurrentSession.DateFrom = Records[0].Date;
            CurrentSession.DateTo = Records[RecordsCount - 1].Date;            
            DbFacade.GetInstance().CreateSession(CurrentSession);
        }
    }
}
